using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;

using Sampler;
using Shapes.Geometry;
using Shapes.Misc;
using Shapes.Misc.Appearance;
using Shapes.Misc.Serialization;

namespace TestGame.Samples
{
    /// <summary>
    /// This class shows the functionality of a ShapeComposition.
    /// The user can create his own composition by clicking with a random shape onto the screen.
    /// He is able to use different functions to compose the shape to the composition.
    /// 
    /// After changing the sample to CollisionSample1 the user can detect collisions with his creation.
    /// </summary>
    public class ShapeCompositionSample : Sample
    {
        #region Fields

        // this singleton is needed to get access to this class from CollisionSample1 
        public static ShapeCompositionSample Instance { get; private set; }
        
        // a helper which lets control the active shape with any input
        // look into the HelperClasses Folder to see how this class works
        TestGame.HelperClasses.CursorHelper _Cursor;

        // the main shape, which is modified by the user
        ShapeComposition _Composition;
        public ShapeComposition Composition { get { return _Composition; } }

        // the function with which the next shape will be added to the composition
        ShapeComposeFunction _CurrentFunction = ShapeComposeFunction.Add;

        // the texture of the main shape
        Texture2D _Texture;

        // the shape which can be added to the composition by clicking
        Shape _MouseShape;
        // the texture of the next shape
        Texture2D _MouseTexture;
        // the color of the next shape. It belongs to the current function.
        Color _MouseColor = Color.Chartreuse;

        // a class which allows to draw a shape into a texture
        TextureGenerator _Generator;

        // a randomizer to create random shapes
        Random _Random;

        #endregion

        public ShapeCompositionSample(TextureGenerator textureGenerator)
            : base("")
        {
            Instance = this; // define singleton

            _Generator = textureGenerator;

            _Cursor = new TestGame.HelperClasses.CursorHelper(Game.GraphicsDevice);
            _Cursor.IsDigitalGamepadMovementEnabled = false;
            _Cursor.IsKeyMovementEnabled = false;
            _Cursor.ButtonPressed += _Cursor_ButtonPressed;

            SetText();
        }


        #region Initialize
        protected override void Initialize()
        {
            _Random = new Random();

            // Details in the 'other methods' region
            CreateNewComposition();
            CreateNewMouseShape();
            // ...
        }
        #endregion

        #region Update


        public override void Update(float seconds,
                                GamePadState currentButtons, GamePadState previousButtons,
                                KeyboardState currentKeys, KeyboardState previousKeys,
                                MouseState currentMouse, MouseState previousMouse)
        {
            _Cursor.Update(seconds, currentButtons, previousButtons, currentKeys, previousKeys, currentMouse, previousMouse);
            // set Shape position to mouse position
            _MouseShape.Position = _Cursor.Position;

            // Check keys
            // Check for CompositionFunction Keys
            if (currentKeys.IsKeyDown(Keys.A) && previousKeys.IsKeyUp(Keys.A))
            {
                SetFunction(ShapeComposeFunction.Add);
            }
            else if (currentKeys.IsKeyDown(Keys.S) && previousKeys.IsKeyUp(Keys.S))
            {
                SetFunction(ShapeComposeFunction.Subtract);
            }
            else if (currentKeys.IsKeyDown(Keys.I) && previousKeys.IsKeyUp(Keys.I))
            {
                SetFunction(ShapeComposeFunction.Invert);
            }
            else if (currentKeys.IsKeyDown(Keys.M) && previousKeys.IsKeyUp(Keys.M))
            {
                SetFunction(ShapeComposeFunction.Mask);
            }
            // check clear Key
            if ((currentKeys.IsKeyDown(Keys.C) && previousKeys.IsKeyUp(Keys.C)) 
                || (currentButtons.IsButtonDown(Buttons.Y) && previousButtons.IsButtonUp(Buttons.Y)))
            {
                CreateNewComposition();
            }

            // transform current shape
            // rotate
            if (currentKeys.IsKeyDown(Keys.Left) || currentButtons.IsButtonDown(Buttons.DPadLeft))
                _MouseShape.Rotation -= seconds;
            if (currentKeys.IsKeyDown(Keys.Right) || currentButtons.IsButtonDown(Buttons.DPadRight))
                _MouseShape.Rotation += seconds;

            // scale
            if (currentKeys.IsKeyDown(Keys.Up) || currentButtons.IsButtonDown(Buttons.DPadUp))
            {
                _MouseShape.Scale = new Vector2(_MouseShape.Scale.X + seconds, _MouseShape.Scale.Y + seconds);
            }
            if (currentKeys.IsKeyDown(Keys.Down) || currentButtons.IsButtonDown(Buttons.DPadDown))
            {
                _MouseShape.Scale = new Vector2(_MouseShape.Scale.X - seconds, _MouseShape.Scale.Y - seconds);
            }

#if !WINDOWS_PHONE && !ZUNE && !XBOX
            // save
            if (!currentKeys.IsKeyDown(Keys.F6) && previousKeys.IsKeyDown(Keys.F6))
            {
                ShapesFactory.SaveXml(@"test.xml", true, _Composition); // inside the bin-folder of the TestGame project
            }
            // load
            if (!currentKeys.IsKeyDown(Keys.F7) && previousKeys.IsKeyDown(Keys.F7))
            {
                _Composition.Dispose();

                Drawing[] shapesInFile = ShapesFactory.LoadXml(@"test.xml", true);
                _Composition = shapesInFile[0] as ShapeComposition;
                _Texture = _Generator.GeometryFilled(_Composition);
            }
#endif
        }

        void _Cursor_ButtonPressed(TestGame.HelperClasses.CursorButton button)
        {
            switch (button)
            {
                case TestGame.HelperClasses.CursorButton.Left:

                    // Add the current shape to the composition with the active function
                    _Composition.Add((Shape)_MouseShape.Clone(), _CurrentFunction);
                    // and generate a new texture from it
                    _Texture = _Generator.GeometryFilled(_Composition);

                    break;
                case TestGame.HelperClasses.CursorButton.Right:

                    CreateNewMouseShape();

                    break;
                case TestGame.HelperClasses.CursorButton.Middle:
                    // set next function
                    SetFunction((ShapeComposeFunction)(((int)_CurrentFunction + 1) % 4));
                    break;
            }

        }
        #endregion

        #region Draw
        public override void Draw(SpriteBatch batch)
        {

            Game.GraphicsDevice.Clear(Color.SlateBlue);

            batch.Begin();

            // Draw the ShapeComposition
            batch.Draw(_Texture, _Composition.Position, null, Color.DarkBlue,
                _Composition.Rotation.ClockwiseRadians, _Composition.Origin, _Composition.Scale, SpriteEffects.None, 0.4f);

            // Draw the Shape linked to the mouse with the color of the current function
            batch.Draw(_MouseTexture, _MouseShape.Position, null, _MouseColor,
                _MouseShape.Rotation.ClockwiseRadians, _MouseShape.Origin, _MouseShape.Scale, SpriteEffects.None, 0.5f);

            batch.End();
        }
        #endregion

        #region other methods

        void SetFunction(ShapeComposeFunction function)
        {
            _CurrentFunction = function;

            switch (function)
            {
                case ShapeComposeFunction.Add:
                    _MouseColor = Color.Chartreuse;
                    break;
                case ShapeComposeFunction.Subtract:
                    _MouseColor = Color.Red;
                    break;
                case ShapeComposeFunction.Invert:
                    _MouseColor = Color.Blue;
                    break;
                case ShapeComposeFunction.Mask:
                    _MouseColor = Color.White;
                    break;
            }
            SetText();
        }


        // resets the Composition
        void CreateNewComposition()
        {
            // create a new initial shape
            Shape startShape = GetRandomShape();
            startShape.Position = new Vector2(350, 275);
            
            // release all references of the old composition, if an old one exists
            if(_Composition != null)
                _Composition.Dispose();

            // create the new composition
            _Composition = new ShapeComposition(startShape);
            // create a texture from it
            _Texture = _Generator.GeometryFilled(_Composition);
        }


        // changes the shape which is linked to the mouse
        void CreateNewMouseShape()
        {
            // release all references of the old shape, if an old one exists
            if (_MouseShape != null)
                _MouseShape.Dispose();

            // create a new shape
            _MouseShape = GetRandomShape();
            // and create a texture from it
#if XNA4
            _MouseTexture = _Generator.GeometryFilledWithBorder(_MouseShape, Color.White * 0.25f, Brush.StandardBrush);
#else
            _MouseTexture = _Generator.GeometryFilledWithBorder(_MouseShape, new Color(Color.White, 64), Brush.StandardBrush);
#endif
        }


        // Generates and returns a random shapes
        Shape GetRandomShape()
        {
            int r = _Random.Next(5);
            switch (r)
            {
                case 0: // Rect
                    return new Rect(_Random.Next(300) + 1, _Random.Next(300) + 1);
                case 1: // Ellipse
                    return new Ellipse(_Random.Next(150) + 1, _Random.Next(150) + 1);
                case 2: // Triangle
                    return new Triangle(GetRandomPosition(300), GetRandomPosition(300), GetRandomPosition(300));
                case 3: // Polygon
                    List<Vector2> corners = new List<Vector2>();
                    int max = 4 + _Random.Next(5);
                    for (int i = 0; i < max; i++)
                    {
                        corners.Add(GetRandomPosition(300));
                    }
                    return new Polygon(new LineStrip(corners.ToArray()));
                case 4: // something else
                    int r2 = _Random.Next(8);
                    switch (r2)
                    {
                        case 0: // SpriteShape
                            return new SpriteShape("mandelbrot", new AlphaThreshold(128));
                        case 1: // Star
                            return new Polygon(GeometryTemplates.CreateStar(4 + _Random.Next(10), _Random.Next(150), _Random.Next(250), 0));
                        case 2: // N-Gon
                            return new Polygon(GeometryTemplates.CreateNGon(3 + _Random.Next(8), _Random.Next(250), 0));
                        case 3: // N-Gramm
                            return new Polygon(GeometryTemplates.CreateNGramm((3 + _Random.Next(3)) * 2 - 1, _Random.Next(250), 0));
                        case 4: // Koch Snowflake
                            return new Polygon(GeometryTemplates.CreateKochSnowflake(_Random.Next(250), _Random.Next(4), 0));
                        case 5: // Arrow
                            return new Polygon(GeometryTemplates.CreateArrow(GetRandomPosition(300), GetRandomPosition(300), _Random.Next(100), _Random.Next(100), _Random.Next(200)));
                        case 6: // Smiley
                            return GeometryTemplates.CreateSmiley(_Random.Next(150));
                        case 7: // Yin & Yang
                            return GeometryTemplates.CreateYinYang(_Random.Next(150), _Random.Next(20), _Random.Next(30));
                    }
                    break;
            }
            return new Rect(10, 10);
        }

        // returns a random Vector2 which is smaller than max
        Vector2 GetRandomPosition(int max)
        {
            return new Vector2(_Random.Next(max), _Random.Next(max));
        }

        // due to displaying the current function 
        // it is required to reset the description...
        void SetText()
        {
            Descripton = "Shape Composition Sample:\n"
            + "Put random shapes together to create a custom form.\n\n"
            + "Left click / press A-Button to add a Shape with the current function.\n"
            + "Right click / press B-Button to get another random shape.\n"
            + "Use the arrow keys / D-Pad to rotate and scale the current shape.\n"
            + "Press F6 to save and F7 to load the current composition\n\n"
            + "Use the X-Button or the following keys to change the function:\n"
            + "A: Add      (Geometry will be added)\n"
            + "S: Subtract (Geometry will cut out itself from the Geometry below)\n"
            + "I: Invert   (geometry below will be cutted out no geometry below will be added)\n"
            + "M: Mask     (only geometry inside the shape will stay)\n\n"
            + "Current function: " + _CurrentFunction.ToString() + "\n\n"
            + "Press C or the Y-Button to restart."
;
        }
        #endregion
    }
}
